//-----------------------------------------------------------------------------
// <copyright file="ClassLoader.cs" company="Justin Wallace Saunders">
//     Copyright (c) Microsoft Public License. All rights reserved.
// </copyright>
// <author name="Justin Wallace Saunders" email="justinsa@live.ca" />
//-----------------------------------------------------------------------------
namespace Community.Tools.Plugins
{
    using System;
    using System.Globalization;
    using System.IO;
    using System.Reflection;

    /// <summary>
    /// Loads assemblies to fetch a dynamic instance of a class.
    /// </summary>
    public static class ClassLoader
    {
        /// <summary>
        /// Gets a dynamic instance of a class.
        /// </summary>
        /// <param name="typeName">The qualified type name to instantiate</param>
        /// <returns>A dynamic instance of a class; or null</returns>
        /// <remarks>
        /// The ClassLoader has pre-defined rules for how it attempts to
        /// ascertain the location of a class. First choice is to look through
        /// the currently calling assembly. The next choice is to attempt to
        /// load the assembly with the same name as <paramref name="typeName"/>
        /// and then look in there. Failing this it will return null.
        /// </remarks>
        public static object GetInstance(string typeName)
        {
            return GetInstance(typeName, null, false);
        }

        /// <summary>
        /// Gets a dynamic instance of a class.
        /// </summary>
        /// <param name="typeName">The qualified type name to instantiate</param>
        /// <param name="assemblyFileName">An external assembly to test if testExternalAssemblies is set</param>
        /// <param name="testExternalAssemblies">a flag to look at external assemblies</param>
        /// <returns>A dynamic instance of a class; or null</returns>
        /// <remarks>
        /// The ClassLoader has pre-defined rules for how it attempts to
        /// ascertain the location of a class. First choice is to look through
        /// the currently calling assembly. The next choice is to attempt to
        /// load the assembly with the same name as <paramref name="typeName"/>
        /// and then look in there. Failing this it will return null.
        /// </remarks>
        public static object GetInstance(string typeName, string assemblyFileName, bool testExternalAssemblies)
        {
            // Check current assembly
            Assembly assembly = Assembly.GetExecutingAssembly();
            object result = GetInstance(typeName, assembly);
            if (result != null)
            {
                return result;
            }

            // Try to load assembly from Assembly Location
            if (assemblyFileName == null || !testExternalAssemblies)
            {
                assemblyFileName = typeName;
            }

            string assemblyName = string.Format(CultureInfo.InvariantCulture, "{0}.dll", assemblyFileName);
            string path = Assembly.GetExecutingAssembly().Location;
            path = Path.GetDirectoryName(path);
            path = string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", path, Path.DirectorySeparatorChar, assemblyName);
            assembly = LoadAssembly(path);
            if (assembly != null)
            {
                result = GetInstance(typeName, assembly);
                if (result != null)
                {
                    return result;
                }
            }

            // Try to load assembly from Assembly Code Base
            path = Assembly.GetExecutingAssembly().CodeBase.Replace(@"file:///", string.Empty);
            path = Path.GetDirectoryName(path);
            path = string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", path, Path.DirectorySeparatorChar, assemblyName);
            assembly = LoadAssembly(path);
            if (assembly != null)
            {
                result = GetInstance(typeName, assembly);
                if (result != null)
                {
                    return result;
                }
            }

            return result;
        }

        /// <summary>
        /// Gets a dynamic instance of a class.
        /// </summary>
        /// <param name="typeName">The qualified type name to instantiate</param>
        /// <param name="assembly">The assembly containing the type</param>
        /// <returns>A dynamic instance of a class; or null</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Any exception is useless to propegate at this point and should be ignored")]
        public static object GetInstance(string typeName, Assembly assembly)
        {
            if (string.IsNullOrEmpty(typeName))
            {
                throw new ArgumentNullException("typeName");
            }

            if (assembly == null)
            {
                throw new ArgumentNullException("assembly");
            }

            object instance = null;

            try
            {
                instance = assembly.CreateInstance(typeName, true);
            }
            catch
            {
                // Nothing we can do here. Let the caller handle getting null back.
                instance = null;
            }

            return instance;
        }

        /// <summary>
        /// Loads an assembly from disk.
        /// </summary>
        /// <param name="filepath">The path to a dynamic link library</param>
        /// <returns>The class instance if found; null otherwise</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Any exception is useless to propegate at this point and should be ignored")]
        private static Assembly LoadAssembly(string filepath)
        {
            if (string.IsNullOrEmpty(filepath))
            {
                throw new ArgumentNullException("filepath");
            }

            if (!System.IO.File.Exists(filepath))
            {
                return null;
            }

            Assembly assembly = null;

            try
            {
                assembly = Assembly.LoadFrom(filepath);
            }
            catch
            {
                // Nothing we can do here. Let the caller handle getting null back.
                assembly = null;
            }

            return assembly;
        }
    }
}